#include <iostream>
#include <string>
#include <cstring>
#include "quickjs.h"
#include "quickjs-libc.h"
#include "../sharedcodes/wdirtools.h"
#include <sstream>
#include <fstream>
#include "../sharedcodes/wstringutils.h"
#include "wjsruntime.h"
#include "quickjs-libc.h"
#include "spdlog/spdlog.h"
#include "spdlog/sinks/stdout_color_sinks.h"
#include "spdlog/sinks/basic_file_sink.h"
#include "../sharedcodes/wdatetime.h"
#include "rapidcsv.h"
#include "../sharedcodes/getopt_pp.h"
#include "dockercurloperator.h"
#include "wpredefinedvolumes.h"
#include <boost/filesystem.hpp>
#include <boost/filesystem/convenience.hpp>

using namespace std;
using namespace GetOpt;

#define INPUT_MODE_JSONFILENAME 0
#define INPUT_MODE_JSONCONTENT 1
#define INPUT_MODE_PLAINTEXT 2

string g_workingdir = "" ;// this is workingdir for one run, support read and write. this is required.
string g_modeldir = "" ;// a readonly dir for model luts and some static files. this is optional.
wPredefinedVolumes g_volumes ;

// !register
//read file all contents into one line.
//if anything wrong return null.
JSValue jsf_readfile_into_oneline(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) {
    if( argc == 1 ) {
        string filename ( JS_ToCString(ctx, argv[0]) ) ;
        string text1line ;
        bool isok = wDirTools::readFullTextIntoOneLine( filename ,text1line ) ;
        if( isok==false ) {
            spdlog::error("wDirTools::readFullTextIntoOneLine failed to read {}." , filename) ;
            return JS_NULL ;
        }else {
            return JS_NewString( ctx, text1line.c_str() );
        }
    }else {
        return JS_NULL ;
    }
}

// !register
//read file all contents with newline.
//if anything wrong return null.
JSValue jsf_readfile_into_lines(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) {
    if( argc == 1 ) {
        string filename ( JS_ToCString(ctx, argv[0]) ) ;
        string textlines ;
        bool isok = wDirTools::readFullTextIntoLines( filename ,textlines ) ;
        if( isok==false ) {
            spdlog::error("wDirTools::readFullTextIntoLines failed to read {}." , filename) ;
            return JS_NULL ;
        }else {
            return JS_NewString( ctx, textlines.c_str() );
        }
    }else {
        return JS_NULL ;
    }
}


// !register
//read file as 2d array  , output is 2d array
//js read_2darray_from_file( filename, separator , isFirstLineTitle )
// isFirstLineTitle true skip first line, else no title.
// a 2d array is [[row0] , [row1] , ... , [rowN] ]
// if good return 2d array , else return null.
JSValue jsf_read_2darray_from_file( JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv ) {
    if( argc==3 ) {
        string filename ( JS_ToCString(ctx, argv[0]) ) ;
        string sep      ( JS_ToCString(ctx, argv[1]) ) ;
        bool   isFirstTitle = JS_ToBool(ctx , argv[2]) ;

        ifstream ifs( filename.c_str() ) ;
        if( ifs.is_open() ) {
            int hasTitle = -1;
            if( isFirstTitle ) hasTitle = 0 ;
            rapidcsv::Document doc(ifs, rapidcsv::LabelParams(hasTitle,-1) ,  rapidcsv::SeparatorParams(sep[0]) );
            size_t colnum = doc.GetColumnCount () ;
            size_t rownum = doc.GetRowCount() ;
            JSValue arr = JS_NewArray(ctx);
            for(int irow = 0 ; irow < rownum; ++ irow ) {
                JSValue arr_row = JS_NewArray(ctx) ;
                for(int icol = 0; icol < colnum;++icol ) {
                    double val1 = doc.GetCell<double>(icol,irow) ;
                    JS_SetPropertyUint32( ctx, arr_row,
                         icol ,  JS_NewFloat64(ctx,val1) );
                }
                JS_SetPropertyUint32( ctx, arr,
                         irow ,  arr_row );
            }
            ifs.close() ;
            return arr ;
        }else{
            spdlog::error("jsf_read_2darray_from_file failed to open {}." , filename) ;
            return JS_NULL ;
        }

    }else {
        return JS_NULL ;
    }
}


// ! register
//write 2D array into csv file. success return true, else return false.
//2D array is [ [row0 ], [row1 ] , ... , [rowN ] ]
//js write2darray_to_file( arr2d , filename )
//good return true, else return false.
JSValue jsf_write2darray_to_file( JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv ) {
    if( argc==2 ) {
        int isarr = JS_IsArray( ctx , argv[0] ) ;
        string filename ( JS_ToCString(ctx, argv[1]) ) ;
        if( isarr==1 ) {
            ofstream ofs(filename.c_str()) ;
            if( ofs.is_open() ) {
                JSValue len_val = JS_GetPropertyStr(ctx, argv[0] , "length");
                int len = 0;
                JS_ToInt32( ctx, &len , len_val);//get array length
                JS_FreeValue(ctx,len_val);
                for(int irow = 0 ; irow < len ; ++ irow ) {
                    JSValue rowValue = JS_GetPropertyUint32(ctx, argv[0] , irow) ;
                    int isarr2 = JS_IsArray( ctx , rowValue ) ;
                    if( isarr2 == 1 ) {
                        JSValue len2_val = JS_GetPropertyStr(ctx, rowValue , "length");
                        int len2 = 0;
                        JS_ToInt32( ctx, &len2 , len2_val);//get array length
                        JS_FreeValue(ctx,len2_val);
                        for(int icol = 0 ; icol < len2; ++ icol ) {
                            JSValue colValue = JS_GetPropertyUint32(ctx, rowValue , icol) ;
                            double tempDval = 0;
                            JS_ToFloat64(ctx,&tempDval , colValue) ;
                            ofs<<tempDval<<"," ;
                            JS_FreeValue(ctx,colValue);
                        }
                        ofs<<endl ;

                    }
                    JS_FreeValue(ctx,rowValue);
                }
                ofs.close() ;
                return JS_TRUE;
            }else{
                spdlog::error("jsf_write2darray_to_file failed to open file {}." , filename) ;
                return JS_FALSE ;
            }
        }else{
            spdlog::error("jsf_write2darray_to_file argv[0] is not js array.") ;
            return JS_FALSE ;
        }
    }else {
        return JS_FALSE ;
    }
}

// ! register
//write string into text file
//js writetext_to_file( text , filename )
//good return true, else return false.
JSValue jsf_writetext_to_file( JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv ) {
    if( argc == 2 ) {
        string text1(     JS_ToCString(ctx, argv[0])  ) ;
        string filename ( JS_ToCString(ctx, argv[1])  ) ;
        ofstream ofs( filename.c_str() ) ;
        if( ofs.is_open() ) {
            ofs<<text1 ;
            ofs.close() ;
            return JS_TRUE ;
        }else{
            return JS_FALSE;
        }
    }else {
        return JS_FALSE ;
    }
}



//call bash command
int bash_operator(const string& taskid, const string& cmd) {
    spdlog::info("bash_operator taskid {}, try to run:" , taskid ) ;
    string logfile = g_workingdir + "/" + taskid + ".log" ;
    string cmd2 = cmd + ">" + logfile ;
    spdlog::info(cmd2) ;
    int ret = system( cmd2.c_str() ) ;
    spdlog::info("return value:{}" , ret) ;
    return ret ;
}

//call bash command
//if cmd ok return command retval of integer, else return -1.
JSValue jsf_bash_operator(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) {
    if( argc==2 ) {
        const char* taskid0 = JS_ToCString(ctx, argv[0]);
        const char* cmd0 = JS_ToCString(ctx, argv[1]);
        string taskid (  taskid0) ;
        string cmd( cmd0 ) ;
        JS_FreeCString(ctx,taskid0);
        JS_FreeCString(ctx,cmd0);
        int reti = bash_operator(taskid,cmd) ;
        JSValue res = JS_NewInt32(ctx,reti) ;
        return res ;
    }else{
        spdlog::error("bash_operator failed to call, argc not 2.") ;
        JSValue res = JS_NewInt32(ctx, -1) ;
        return res ;
    }
}

// int docker_operator()
int docker_operator(string optid,string imgName,vector<string>& volumes,string command) {
    DockerCurlOperator oper ;
    bool imgok = oper.isImageExists(imgName);
    if( imgok==false ) {
        spdlog::error("docker_operator {} failed , can not find docker image of {}.",optid,imgName) ;
        return -2 ;
    }
    vector<DockerMount> mounts ;
    //following code need check host dir is valid, but now i have no time to do. 2023-10-27
    mounts.push_back( DockerMount("/workingdir",g_workingdir,false) ) ;//read and write
    if( g_modeldir!="" ) {
        mounts.push_back( DockerMount("/modeldir", g_modeldir , true) ) ;//read only
    }

    //item in volumes should be like /TargetFY3D:/HostFY3D
    for(int ivol = 0 ; ivol < volumes.size() ; ++ ivol ) {
        vector<string> vol1tokens = wStringUtils::splitString(volumes[ivol],":") ;
        if( vol1tokens.size()!=2 ) {
            spdlog::error("docker_operator {} failed , can not parse volume binding of {}", optid,volumes[ivol] );
            return -3 ;
        }
        string targetDir = vol1tokens[0] ;
        string hostDirTag = vol1tokens[1] ;
        string realHostDir = g_volumes.getHostDirByTag(hostDirTag) ;
        if( realHostDir=="" ) {
            spdlog::error("docker_operator {} failed , can not find volume binding for {}",optid, hostDirTag);
            return -4 ;
        }
        mounts.push_back( DockerMount(targetDir , realHostDir , true) );//read only
    }

    spdlog::info("docker_operator operatorid {}, running " , optid ) ;
    string logfile = g_workingdir + "/" + optid + ".log" ;
    string error ;
    vector<string> commandtokens = wStringUtils::splitString(command," ") ;
    if( commandtokens.size()==0 ) {
        spdlog::info("docker_operator {} failed , command is emtpy." ,optid) ;
        return -5 ;
    }
    bool ok = oper.runCommandsAndRemove(imgName,mounts,logfile,commandtokens,error) ;// here here here
    if( ok==false ) {
        spdlog::error("docker_operator {} failed , running error: {}.",optid,error) ;
        return -6 ;
    }
    spdlog::info("docker_operator {} runs done.",optid ) ;
    return 0 ;
}

// js call docker command
// docker_operator( "id", "ImageName", ["tarDir1:srcDir1","tarDir2:srcDir2",...], "command" );
// all volumes are readonly
// will bind modeldir (readonly) and workingdir(read/write)
// return the docker command return value.
JSValue jsf_docker_operator(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) {
    if( argc==4 ) {
        string optId ( JS_ToCString(ctx, argv[0]) ) ;
        string imageName ( JS_ToCString(ctx, argv[1]) ) ;//docker image name
        JSValue len_val = JS_GetPropertyStr(ctx,argv[2],"length") ;
        int len = 0;
        JS_ToInt32( ctx, &len , len_val);//get array length
        JS_FreeValue(ctx,len_val);
        vector<string> volumes;
        for(int ivol = 0 ; ivol < len ; ++ ivol ) {
            JSValue jsvolstr = JS_GetPropertyUint32(ctx, argv[2] , ivol) ;
            string volstr(JS_ToCString(ctx,jsvolstr)) ;
            volumes.push_back(volstr) ;
            JS_FreeValue(ctx,jsvolstr);
        }
        string cmd( JS_ToCString(ctx, argv[3]) ) ;
        int reti = docker_operator(optId,imageName,volumes,cmd) ;
        JSValue res = JS_NewInt32(ctx,reti) ;
        return res ;
    }else{
        spdlog::error("docker_operator failed to call, argc not 4.") ;
        JSValue res = JS_NewInt32(ctx, -1) ;
        return res ;
    }
}


//log, multi params, always return true.
JSValue jsf_log(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) {
    string logtext("js: ");
    for(int i = 0 ; i<argc ; ++ i )
    {
        const char* tstr =  JS_ToCString(ctx, argv[i]);
        if( tstr !=nullptr ) {
            string str1 ( tstr ) ;
            logtext += str1 + " " ;
            JS_FreeCString(ctx, tstr);
        }else{
            spdlog::warn("jsf_log JS_ToCString get null char* ");
            logtext += " <null char*> " ;
        }
    }
    spdlog::info( logtext ) ;
    return JS_TRUE ;
}

//scanfiles, javascript function to scan files in some dir. return is array of full filename.
//usage in js:
//var filenames = scanfiles( '/some/dir', isRecursive(true|false) , 'filename_prefix_can_be_empty', 'filename_tail_can_be_empty' );
JSValue jsf_scanfiles(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) {
    string prefix;
    string tail;
    string dir;
    bool recursively = false;
    if( argc==1 ) {
        dir = JS_ToCString(ctx, argv[0]);
    }else if( argc==2 ) {
        dir = JS_ToCString(ctx, argv[0]);
        recursively = JS_ToBool(ctx,argv[1]) ;
    }else if( argc==3 ) {
        dir = JS_ToCString(ctx, argv[0]);
        recursively = JS_ToBool(ctx,argv[1]) ;
        prefix = JS_ToCString(ctx, argv[2]);
    }
    else if( argc==4 ) {
        dir = JS_ToCString(ctx, argv[0]);
        recursively = JS_ToBool(ctx,argv[1]) ;
        prefix = JS_ToCString(ctx, argv[2]);
        tail = JS_ToCString(ctx, argv[3]);
    }else {
        spdlog::info( string("js jsf_scanfiles error, params not 1 nor 3.") ) ;
        return JS_NULL;// bad params
    }
    JSValue outArr = JS_NewArray(ctx);
    vector<string> filenames;
    filenames.reserve(128) ;
    if( recursively == true ) {
        wDirTools::getDirFilesRecursive2(dir,prefix,tail,filenames) ;
    }else {
        wDirTools wdir;
        wdir.getDirFiles(dir,prefix,tail,filenames) ;
    }
    int ii = 0;
    for(auto it = filenames.begin();it!=filenames.end();++it) {
        JSValue jstr = JS_NewString(ctx, (*it).c_str());
        if( JS_IsException(jstr) ) {
            spdlog::warn("JS_NewString is null for cstr {}", *it);
            JS_SetPropertyUint32(ctx, outArr,
                         ii , JS_NULL);
        }else{
            JS_SetPropertyUint32(ctx, outArr,
                         ii , jstr);
        }
        ii++;
        //JS_FreeValue(ctx,jstr);// we put the newString into array, refcount is 1, so we do not free jstr, the array will free the jstrs.
    }
    return outArr ;
}





/// @brief for each filename scanned, just run the function. no returns.
// usage in js:
// scanfiles2run( function(fname){} , '/some/dir', isRecursive(true|false) , 'filename_prefix_can_be_empty', 'filename_tail_can_be_empty' );
JSValue jsf_scanfiles2run(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) {
    JSValue theFunction;
    string prefix;
    string tail;
    string dir;
    bool recursively = false;
    if( argc<2 ) {
        spdlog::error("Error, jsf_scanfiles2run, argc should be >= 2.") ;
        return JS_EXCEPTION;
    }
    JS_BOOL argv0Isfunc = JS_IsFunction(ctx, argv[0]);
    if( argv0Isfunc==0 ) {
        spdlog::error("Error, jsf_scanfiles2run, argv[0] is not a function.") ;
        return JS_EXCEPTION;
    }
    theFunction = argv[0] ;
    dir = JS_ToCString(ctx, argv[1]);
    if( argc>2 ) {
        recursively = JS_ToBool(ctx,argv[2]) ;
    }
    if( argc> 3) {
        prefix = JS_ToCString(ctx, argv[3]);
    }
    if( argc>4 ) {
        tail = JS_ToCString(ctx, argv[4]);
    }
    vector<string> filenames;
    filenames.reserve(1024) ;
    if( recursively == true ) {
        wDirTools::getDirFilesRecursive2(dir,prefix,tail,filenames) ;
    }else {
        wDirTools wdir;
        wdir.getDirFiles(dir,prefix,tail,filenames) ;
    }
    spdlog::info("total scan files count {}." , filenames.size()) ;
    int ii = 0;
    for(auto it = filenames.begin();it!=filenames.end();++it) {
        ii++;
        JSValue fname = JS_NewString(ctx, (*it).c_str() ) ;// JS_NewString must be end with JS_FreeValue
        if( JS_IsException(fname) ) {
            spdlog::warn("JS_NewString of cstr {} face exception.", *it) ;
        }
        else{
            JSValueConst* theArgv = &fname ;
            JS_Call(ctx, theFunction, this_val,
                    1 , theArgv );
        }
        JS_FreeValue(ctx,fname);

    }
    return JS_NULL ;
}


//mkdir recursively
/// var isok = mkdir("/some/multi/level/dir");
///@retval return true if dir exists or create sucess, return false create failed.
JSValue jsf_mkdir(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) {

    if( argc!=1 ) {
        JSValue outval = JS_NewBool(ctx,false);
        return outval;
    }
    string dir = JS_ToCString(ctx, argv[0]);
    if( boost::filesystem::is_directory(dir) ) {
        return JS_NewBool(ctx,true);
    }else{
        try{
            if( boost::filesystem::create_directories(dir) ){
                return JS_NewBool(ctx,true);
            }else{
                return JS_NewBool(ctx,false);
            }
        }catch(std::exception& e) {
            spdlog::error(  string("mkdir() exception: ")+e.what() );
            return JS_NewBool(ctx,false);
        }catch(...){
            spdlog::error("unknown exception for mkdir()");
            return JS_NewBool(ctx,false);
        }
    }
}

JSValue jsf_basename(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) {
    if( argc==1 ) {
        if( JS_IsString(argv[0]) ) {
            const char* cstr = JS_ToCString(ctx,argv[0]) ;
            if( cstr!=nullptr ) {
                string bname = boost::filesystem::basename(string(cstr) ) ;
                JS_FreeCString(ctx,cstr) ;
                return JS_NewString(ctx, bname.c_str()) ;
            }
        }
    }
    return JS_NULL;
}

JSValue jsf_fileExists(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) {
    if( argc==1 ) {
        if( JS_IsString(argv[0]) ) {
            const char* cstr = JS_ToCString(ctx,argv[0]) ;
            if( cstr!=nullptr ) {
                bool isExists = boost::filesystem::exists(cstr) ;
                JS_FreeCString(ctx,cstr) ;
                if( isExists==1 ){
                    return JS_TRUE;
                }else{
                    return JS_FALSE;
                }
            }

        }
    }
    return JS_NULL;
}

JSValue jsf_extensionName(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) {
    if( argc==1 ) {
        if( JS_IsString(argv[0]) ) {
            const char* cstr = JS_ToCString(ctx,argv[0]) ;
            if( cstr!=nullptr ) {
                string extname = boost::filesystem::extension(cstr) ;
                JS_FreeCString(ctx,cstr) ;
                return JS_NewString(ctx,extname.c_str()) ;
            }

        }
    }
    return JS_NULL;
}

JSValue jsf_changeExtension(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) {
    if( argc==2 ) {
        if( JS_IsString(argv[0]) && JS_IsString(argv[1]) ) {
            const char* cstr = JS_ToCString(ctx,argv[0]) ;
            const char* extstr = JS_ToCString(ctx,argv[1]) ;
            if( cstr!=nullptr && extstr!=nullptr ) {
                string fullname(cstr) ;
                string newext(extstr);
                JS_FreeCString(ctx,cstr) ;
                JS_FreeCString(ctx,extstr) ;
                string newName = boost::filesystem::change_extension( boost::filesystem::path(fullname), boost::filesystem::path(newext)).c_str() ;
                return JS_NewString(ctx,newName.c_str()) ;
            }
            if(cstr!=nullptr)JS_FreeCString(ctx,cstr) ;
            if(extstr!=nullptr)JS_FreeCString(ctx,extstr) ;
        }
    }
    return JS_NULL;
}


void printShowHelp() {
    cout<<"--- start of help info ---"<<endl;
    cout<<"build-in functions:"<<endl ;
    cout<<"var jsonObj = json_from_file(filename);"<<endl;
    cout<<"var retval=bash_operator('taskid','commandstring') ; "<<endl;
    cout<<"var retval=docker_operator('taskid','imageName', ['tardir:hostdir',...],'commandstring'); "<<endl;
    cout<<"log('text') ; "<<endl;
    cout<<"var onelinetext=readfile_into_oneline(filename) ; "<<endl;
    cout<<"var lines =readfile_into_oneline(filename) ; "<<endl;
    cout<<"var rowfirstArray=read_2darray_from_file(filename,'separator',isSkipFirst) ; "<<endl;
    cout<<"var res=write2darray_to_file(array2d,filename) ; "<<endl;
    cout<<"var res=writetext_to_file(text,filename) ; "<<endl;
    cout<<"var isok = mkdir('/some/multi/level/dir');"<<endl;
    cout<<"var fnames=scanfiles('/some/dir'[,isRecursive(true|false),'filename_prefix_can_be_empty', 'filename_tail_can_be_empty']); "<<endl;
    cout<<"  For some unknown reasons runtime can not allocate too much String object in the output array of scanfiles, so I add a function scanfiles2run."<<endl;
    cout<<"    scanfiles2run( function(fname){...}, '/some/dir' [, isRecursive, filename_prefix, filename_tail] ); this function return nothing, but run for each filename."<<endl;
    cout<<"var baseNameWithoutExt=basename(fullfilename);// /some/dir/fullname.txt -> fullname "<<endl;
    cout<<"var isExist=fileExists(filename);// true or false"<<endl;
    cout<<"var extName=extensionName(fullfilename);// /some/dir/fullname.txt -> .txt"<<endl;
    cout<<"var newName=changeExtension(fullfilename,newExt);// /some/dir/fullname.txt -> /some/dir/fullname.newext"<<endl;
    cout<<"--- end of help info ---"<<endl;
}


int main(int argc,char* argv[])
{
    cout << "A task flow worker and executor based on Quickjs. 2023-7-30" << endl;
    string version = "v1.1.0.6d" ;//add shell json
    version = "v1.2.0.10 2023-10-27" ;//add docker operator release!
    version = "v1.3.0.11d 2023-11-08";//add description. use -i plain text as input.
    version = "v1.4.0.2 feature: add scanfiles js function 2023-11-26";
    version = "v1.5.0.1 feature: workingdir is optional,default use current dir './' as working dir .";
    version = "v1.6.0.1 feature: js function 'mkdir' to make directories." ;// 2023-12-08
    version = "v1.8.0.3 feature: scanfiles2run() ." ;// 2023-12-18
    version = "v2.0.0.1 feature: reversion into 2.x, add add Date object ." ;//2024/1/25

    cout<<version<<endl ;

    cout<<"usage:js_taskflow_worker [-h] [--pv /some/json] [-m /some/model/dir/] [-w /some/workingdir/] -s dagscripts.js [-f inputParams.json]|[-j {...jsoncontent...}]|[-p plaintextAsInputStr] "<<endl ;
    cout<<" -f: 1st use. input params json filename , if use this then program will igonre -j and -p. optional."<<endl ;
    cout<<" -j: 2nd use. input params json content from shell. optional."<<endl ;
    cout<<" -p: 3rd use. input plain text not json. optional."<<endl ;
    cout<<" both -f and -j are optional, if these two ignored, the script will read a empty object."<<endl ;
    cout<<"--pv: a json file for predefined volumes for docker_operator. must abs-path. e.g. {volumes:[{tag:/FY3D,hostdir:/mnt/FY3/FY3D}...]}. optional"<<endl;
    cout<<"-m: the model dir for some static files. must abs-path . optional"<<endl ;
    cout<<"-w: the working dir for read and write. if this is relative path it will not used in docker_operator. required."<<endl ;
    cout<<"-s: the script filename. required."<<endl;
    cout<<"-h: print help infos."<<endl;
    cout<<"--mem-limit: memory limit in MB, optional."<<endl;
    cout<<"---------"<<endl;
    cout<<"var dag={workingdir,modeldir};"<<endl ;
    cout<<"var input=jsoncontent|'plaintext';"<<endl ;
    cout<<"---------"<<endl;


    GetOpt_pp ops(argc, argv);
    string workingdir;
    string modeldir ;//model static files dir. optional.
    string scriptfile ;
    string inputjsonfilename;
    string inputjsoncontent ;
    string inputplaintext;
    string volumesfilename;
    size_t memlimitMB=0;
    bool showHelp = false;

    ops>>Option('m',modeldir) ;
    ops>>Option('w',workingdir) ;
    ops>>Option('s',scriptfile) ;
    ops>>Option('f',inputjsonfilename) ;
    ops>>Option('j',inputjsoncontent) ;
    ops>>Option('p',inputplaintext) ;
    ops>>Option("pv" , volumesfilename) ;
    ops>>OptionPresent('h', showHelp) ;
    ops>>Option("mem-limit", memlimitMB) ;

    if( showHelp==true ) {
        printShowHelp() ;
    }

    if( workingdir=="") {
        cout<<"use working dir of './'. "<<endl ;
        workingdir = "./" ;
    }
    if( scriptfile=="") {
        cout<<"Error,-s is emtpy."<<endl ;
        return 11 ;
    }

    int inputMode = INPUT_MODE_JSONFILENAME;
    if( inputjsonfilename.length()> 0 ) {
        inputjsoncontent = "" ;
        inputMode = INPUT_MODE_JSONFILENAME;
    }else if(inputjsoncontent.length()>0) {
        inputMode = INPUT_MODE_JSONCONTENT;
    }else if( inputplaintext.length()>0 ) {
        inputMode = INPUT_MODE_PLAINTEXT;
    }
    else{
        inputMode = INPUT_MODE_JSONCONTENT;
        inputjsoncontent = "{}" ;// use emtpy object as input json.
    }

    int64_t start_ms = wDatetime::currentMilliSeconds() ;

    cout<<"modeldir "<<modeldir<<endl;
    cout<<"workingdir "<<workingdir<<endl ;
    cout<<"scriptfile "<<scriptfile<<endl ;
    cout<<"inputjsonfilename "<<inputjsonfilename<<endl ;
    cout<<"inputjsoncontent "<<inputjsoncontent<<endl ;
    cout<<"inputplaintext "<<inputplaintext<<endl ;
    cout<<"inputmode(0-jsonfilename,1-jsoncontent,2-plaintext) "<<inputMode<<endl;
    cout<<"volumesfilename "<<volumesfilename<<endl;
    cout<<"memlimitMB "<<memlimitMB<<endl;

    string logfile = workingdir + "/task.log" ;
    cout<<"logfile "<<logfile<<endl ;

    std::vector<spdlog::sink_ptr> sinks;
    sinks.push_back(std::make_shared<spdlog::sinks::stdout_color_sink_mt>());
    sinks.push_back(std::make_shared<spdlog::sinks::basic_file_sink_mt>(logfile));
    auto combined_logger = std::make_shared<spdlog::logger>("l", begin(sinks), end(sinks));
    spdlog::register_logger(combined_logger);
    spdlog::set_default_logger(combined_logger);
    spdlog::flush_every(std::chrono::seconds(1));

    spdlog::info("js_taskflow_worker version {}", version ) ;
    spdlog::info("modeldir {}", modeldir ) ;
    spdlog::info("workingdir {}", workingdir ) ;
    spdlog::info("scriptfile {}", scriptfile ) ;
    spdlog::info("inputjsonfilename {}", inputjsonfilename ) ;
    spdlog::info("inputjsoncontent {}", inputjsoncontent ) ;
    spdlog::info("inputplaintext {}", inputplaintext) ;
    spdlog::info("inputmode(0-jsonfilename,1-jsoncontent,2-plaintext) {}",inputMode) ;
    spdlog::info("volumesfilename {}", volumesfilename);
    spdlog::info("logfile {}", logfile ) ;
    spdlog::info("memlimitMB {}",memlimitMB) ;

    string error ;

    g_workingdir = workingdir ;//also workingdir
    g_modeldir = modeldir ;//this is optional
    if( volumesfilename != "") {
        bool volok = g_volumes.readFromFile(volumesfilename,error);
        if( volok==false ) {
            spdlog::error("g_volumes.readFromFile {} failed. Error info: {}",volumesfilename,error) ;
            return 15 ;
        }
    }else {
        spdlog::info("no need volumes.") ;
    }

    spdlog::info("reading script" ) ;
    string inputText, scriptText ;


    bool ok1 = wDirTools::readFullTextIntoLines(scriptfile,scriptText) ;
    if( ok1==false ) {
        spdlog::error("failed to open script file {}" , scriptfile) ;
        return 12 ;
    }

    if( inputMode==INPUT_MODE_JSONCONTENT ) {
        spdlog::info("using shell json content" ) ;
        inputText = inputjsoncontent ;
    }else if( inputMode==INPUT_MODE_JSONFILENAME )  {
        //read from json filename
        spdlog::info("reading input json content from {}" , inputjsonfilename) ;
        bool ok2 = wDirTools::readFullTextIntoLines(inputjsonfilename,inputText) ;
        if( ok2==false ) {
            spdlog::error("failed to open input file {}" , inputjsonfilename) ;
            return 13 ;
        }
    }

    stringstream ss ;

    //js json_from_file(filename)
    ss<<"function json_from_file(filename){ var text1=readfile_into_oneline(filename) ;  return JSON.parse(text1); }\n\n\n" ;

    ss<<"var dag={"
        << "  \"workingdir\":\"" << g_workingdir  << "\" "
        << ", \"modeldir\":\""   << g_modeldir    <<"\" "
        << "}; \n" ;
    ss<<"var context=dag;\n" ;

    if( inputMode==INPUT_MODE_JSONCONTENT || inputMode==INPUT_MODE_JSONFILENAME ) {
        ss<<"var input="<< inputText <<";\n" ;
    }else if( inputMode==INPUT_MODE_PLAINTEXT ) {
        ss<<"var input=\""<< inputplaintext <<"\";\n" ;
    }

    ss<<scriptText ;

    string fullscript = ss.str() ;
    spdlog::info("write full script" ) ;
    string fullscriptfilename = g_workingdir + "/full.js" ;
    ofstream ofs(fullscriptfilename.c_str()) ;
    if( ofs.is_open() ) {
        ofs<<fullscript ;
        ofs.close() ;
    }else {
        spdlog::error("failed to write full script {}" , fullscriptfilename) ;
        return 14 ;
    }

    spdlog::info("WJsRuntime init" ) ;
    WJsRuntime wjs ;
    bool jsok = wjs.init( memlimitMB, error) ;
    if( jsok==false ) {
        spdlog::error("failed to init WJsRuntime {}" , error) ;
        return 21 ;
    }

    JSValue global_obj;
    global_obj = JS_GetGlobalObject(wjs.getContext());
    //js  bash_operator(taskid, command)
    JS_SetPropertyStr(wjs.getContext(),
                      global_obj, "bash_operator",
                      JS_NewCFunction(wjs.getContext(),
                                    jsf_bash_operator, "bash_operator", 2)
                      );

    //js  docker_operator( "id", "ImageName", ["tardir1:hostdir1","tardir2:hostdir2",...], "command" );
    JS_SetPropertyStr(wjs.getContext(),
                      global_obj, "docker_operator",
                      JS_NewCFunction(wjs.getContext(),
                                    jsf_docker_operator, "docker_operator", 4)
                      );

    //js log(...)
    JS_SetPropertyStr(wjs.getContext(),
                      global_obj, "log",
                      JS_NewCFunction(wjs.getContext(),
                                    jsf_log, "log", 1)
                      );
    //js readfile_into_oneline( filename )
    JS_SetPropertyStr(wjs.getContext(),
                      global_obj, "readfile_into_oneline",
                      JS_NewCFunction(wjs.getContext(),
                                    jsf_readfile_into_oneline, "readfile_into_oneline", 1)
                      );
    //js readfile_into_lines( filename )
    JS_SetPropertyStr(wjs.getContext(),
                      global_obj, "readfile_into_lines",
                      JS_NewCFunction(wjs.getContext(),
                                    jsf_readfile_into_lines, "readfile_into_lines", 1)
                      );
    //js read_2darray_from_file(filename,sep,skipfirst)
    JS_SetPropertyStr(wjs.getContext(),
                      global_obj, "read_2darray_from_file",
                      JS_NewCFunction(wjs.getContext(),
                                    jsf_read_2darray_from_file, "read_2darray_from_file", 1)
                      );
    //js write2darray_to_file( arr2d , filename )
    JS_SetPropertyStr(wjs.getContext(),
                      global_obj, "write2darray_to_file",
                      JS_NewCFunction(wjs.getContext(),
                                    jsf_write2darray_to_file, "write2darray_to_file", 2)
                      );
    //js writetext_to_file(text,filename)
    JS_SetPropertyStr(wjs.getContext(),
                      global_obj, "writetext_to_file",
                      JS_NewCFunction(wjs.getContext(),
                                    jsf_writetext_to_file, "writetext_to_file", 2)
                      );
    //js scanfiles(...)
    JS_SetPropertyStr(wjs.getContext(),
                      global_obj, "scanfiles",
                      JS_NewCFunction(wjs.getContext(),
                                    jsf_scanfiles , "scanfiles", 1)
                      );
    //js scanfiles2run( function(fname){},'/some/dir',isRecur,prefix,tail);
    JS_SetPropertyStr(wjs.getContext(),
                      global_obj, "scanfiles2run",
                      JS_NewCFunction(wjs.getContext(),
                                    jsf_scanfiles2run , "scanfiles2run", 2)
                      );
    //js mkdir(dirpath)
    JS_SetPropertyStr(wjs.getContext(),
                      global_obj, "mkdir",
                      JS_NewCFunction(wjs.getContext(),
                                    jsf_mkdir , "mkdir", 1)
                      );

    //var bname=basename(fullname);
    JS_SetPropertyStr(wjs.getContext(),
                      global_obj, "basename",
                      JS_NewCFunction(wjs.getContext(),
                                    jsf_basename , "basename", 1)
                      );
    //var extname=extensionName(fullname);
    JS_SetPropertyStr(wjs.getContext(),
                      global_obj, "extensionName",
                      JS_NewCFunction(wjs.getContext(),
                                    jsf_extensionName , "extensionName", 1)
                      );
    //var newname=changeExtension(fullname,newext);
    JS_SetPropertyStr(wjs.getContext(),
                      global_obj, "changeExtension",
                      JS_NewCFunction(wjs.getContext(),
                                    jsf_changeExtension , "changeExtension", 2)
                      );
    //var isExtists=fileExists(fullname);
    JS_SetPropertyStr(wjs.getContext(),
                      global_obj, "fileExists",
                      JS_NewCFunction(wjs.getContext(),
                                    jsf_fileExists , "fileExists", 1)
                      );


    spdlog::info("running full script..." ) ;
    JSValue runfullscriptResult = JS_Eval( wjs.getContext() , fullscript.c_str() , fullscript.length() , "<eval>",  0) ;
    if( JS_VALUE_GET_TAG(runfullscriptResult)== JS_TAG_EXCEPTION     ) {
        JSValue exceptionvalue = JS_GetException( wjs.getContext() );
        const char * exceptionCharbuff = JS_ToCString( wjs.getContext() , exceptionvalue ) ;
        spdlog::info("exception : {} " , exceptionCharbuff ) ;
        JS_FreeValue(wjs.getContext() , exceptionvalue);
    }


    JS_FreeValue( wjs.getContext() , global_obj ) ;
    JS_FreeValue( wjs.getContext() , runfullscriptResult ) ;

    {
        int64_t end_ms = wDatetime::currentMilliSeconds() ;
        int fullseconds = (end_ms - start_ms) / 1000 ;
        int tminutes = fullseconds / 60 ;
        int tsecs = fullseconds%60 ;
        spdlog::info("duration {}m {}s " , tminutes , tsecs) ;
    }

    spdlog::info("done." ) ;
    return 0;
}
